package test

/*
Copyright 2021-2025 The k8gb Contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

import (
	"fmt"
	"k8gbterratest/utils"
	"testing"

	"github.com/stretchr/testify/require"
)

func abstractTestFullRoundRobin(t *testing.T, testPrefix string, workflows []*utils.Workflow) {
	if len(workflows) < 2 || len(workflows) > 8 {
		t.Logf("Number of cluster must be in the interval [2,8]")
		t.FailNow()
	}
	clusterCount := len(workflows)
	t.Logf("Running TestFullRoundRobin for %d clusters", clusterCount)
	tags := []string{"eu", "us", "cz", "af", "ru", "ap", "uk", "ca"}
	var instances []*utils.Instance

	// start all the test apps on all the clusters
	for i, workflow := range workflows {
		workflow = workflow.WithTestApp(tags[i])
		instance, er := workflow.Start()
		require.NoError(t, er)
		instances = append(instances, instance)
		defer instance.Kill()
	}
	var err error
	t.Run(fmt.Sprintf("%s round-robin on %d concurrent clusters with podinfo running", testPrefix, len(workflows)), func(t *testing.T) {
		for _, ins := range instances {
			err = ins.WaitForAppIsRunning()
			require.NoError(t, err)
		}
	})

	// at the beginning, it should contain all the targets
	var workingTargets []string
	for _, ins := range instances {
		workingTargets = append(workingTargets, ins.GetLocalTargets()...)
	}
	t.Run(fmt.Sprintf("%s all %d clusters should be interconnected", testPrefix, len(workflows)), func(t *testing.T) {
		allShouldExpectTheseTargets(t, instances, workingTargets)
	})

	// kill the apps on clusters one by one and expect less and less targets to be available
	for i, instance := range instances {
		t.Run(fmt.Sprintf("%s kill podinfo on cluster %d (%s)", testPrefix, i+1, tags[i]), func(t *testing.T) {
			workingTargets = distinct(subtract(workingTargets, instance.GetLocalTargets()))
			t.Logf("New expected targets: %v", workingTargets)
			instance.StopTestApp()
			allShouldExpectTheseTargets(t, instances, workingTargets)
		})
	}

	// start the test apps again on each cluster and check if the targets start appearing
	for i, instance := range instances {
		t.Run(fmt.Sprintf("%s start podinfo on cluster %d (%s)", testPrefix, i+1, tags[i]), func(t *testing.T) {
			instance.StartTestApp()
			workingTargets = distinct(append(workingTargets, instance.GetLocalTargets()...))
			t.Logf("New expected targets: %v", workingTargets)
			allShouldExpectTheseTargets(t, instances, workingTargets)
		})
	}
}

func allShouldExpectTheseTargets(t *testing.T, instances []*utils.Instance, workingTargets []string) {
	for _, instance := range instances {
		err := instance.WaitForExpected(workingTargets)
		require.NoError(t, err)
	}
}

// helper function for subtracting slice from slice (in O(n))
func subtract(from, what []string) (diff []string) {
	aux := make(map[string]bool, len(what))
	for _, w := range what {
		aux[w] = true
	}
	for _, f := range from {
		if _, found := aux[f]; !found {
			diff = append(diff, f)
		}
	}
	return
}

// helper function for de-duplicating elements in slice (in O(n))
func distinct(s []string) (uniq []string) {
	aux := make(map[string]bool)
	for _, item := range s {
		if _, found := aux[item]; !found {
			aux[item] = true
			uniq = append(uniq, item)
		}
	}
	return
}
